package org.twdata.pipeline;

import java.io.*;

import java.util.*;
import javax.xml.parsers.*;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.xml.*;
import org.springframework.core.io.InputStreamResource;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

/**
 *  Convenience factory that builds a map of pipelines built using an XML file
 *  describing pipelines and and a Spring XML configuration file. Configurable
 *  to allow different tag names and id's to locate major Spring-configured
 *  beans.
 */
public class XMLPipelineFactory implements PipelineFactory {

    private String pipelineTag = "pipeline";
    private String generatorTag = "generator";
    private String transformerTag = "transformer";
    private String serializerTag = "serializer";

    private String pipelineBean = "pipeline";
    private String stageBuilderBean = "stageBuilder";

    private Map pipelines;


    /**  Default constructor */
    public XMLPipelineFactory() { }


    /**
     *  Constructor shortcut that loads pipelines
     *
     *@param  springConfig           Spring XML inputstream
     *@param  pipelineConfig         Pipeline XML inputstream
     *@exception  PipelineException  If something goes wrong
     */
    public XMLPipelineFactory(InputStream springConfig, InputStream pipelineConfig)
        throws PipelineException {

        loadPipelines(springConfig, pipelineConfig);
    }


    /**
     *  Constructor shortcut that loads pipelines
     *
     *@param  springFactory          Spring BeanFactory
     *@param  pipelineConfig         Pipeline XML inputstream
     *@exception  PipelineException  If something goes wrong
     */
    public XMLPipelineFactory(BeanFactory springFactory, InputStream pipelineConfig)
        throws PipelineException {

        loadPipelines(springFactory, pipelineConfig);
    }

    /**
     *  Sets the name of the <code>MutablePipeline</code> bean to use when
     *  creating a new pipeline.
     *
     *@param  id  The id the bean is configured as in the Spring config
     */
    public void setPipelineBean(String id) {
        pipelineBean = id;
    }


    /**
     *  Sets the name of the <code>StageBuilder</code> bean to use when creating
     *  a stage builder.
     *
     *@param  id  The id the bean is configured as in the Spring config
     */
    public void setStageBuilderBean(String id) {
        stageBuilderBean = id;
    }


    /**
     *  Sets the name of the pipeline tag
     *
     *@param  name  The new pipeline tag value
     */
    public void setPipelineTag(String name) {
        pipelineTag = name;
    }


    /**
     *  Sets the name of the generator tag
     *
     *@param  name  The new generator tag value
     */
    public void setGeneratorTag(String name) {
        generatorTag = name;
    }


    /**
     *  Sets the name of the transformer tag
     *
     *@param  name  The new transformer tag value
     */
    public void setTransformerTag(String name) {
        transformerTag = name;
    }


    /**
     *  Sets the name of the serializer tag
     *
     *@param  name  The new serializer tag value
     */
    public void setSerializerTag(String name) {
        serializerTag = name;
    }

    /**
     *  Loads the pipelines using Spring configuration and XML pipeline
     *  definitions. The Spring XML configuration defines and wires together the
     *  framework, while the XML pipeline configuration actually defines the
     *  pipelines.
     *
     *@param  springConfig           The Spring XML configuration inputstream
     *@param  pipelineConfig         The pipeline XML configuration inputstream
     *@exception  PipelineException  If something goes wrong
     */
    public void loadPipelines(InputStream springConfig, InputStream pipelineConfig) 
        throws PipelineException {
        if (springConfig == null) {
            springConfig = XMLPipelineFactory.class.getResourceAsStream("/pipelineConfig.xml");
        }
        BeanFactory beanfactory = new XmlBeanFactory(new InputStreamResource(springConfig));
        loadPipelines(beanfactory, pipelineConfig);
    }    

       
 
    /**
     *  Loads the pipelines using Spring factory and XML pipeline
     *  definitions. The Spring XML configuration defines and wires together the
     *  framework, while the XML pipeline configuration actually defines the
     *  pipelines.
     *
     *@param  springFactory          The Spring BeanFactory
     *@param  pipelineConfig         The pipeline XML configuration inputstream
     *@exception  PipelineException  If something goes wrong
     */
    public void loadPipelines(BeanFactory springFactory, InputStream pipelineConfig)
        throws PipelineException {
        pipelines = new HashMap();
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            parser.parse(pipelineConfig, new PipelineParser(springFactory));
        } catch (Exception ex) {
            throw new PipelineException(ex);
        }
    }


    /**
     *  Gets the named pipeline
     *
     *@param  name  The name
     *@return       The pipeline
     */
    public Pipeline getPipeline(String name) {
        return (Pipeline) pipelines.get(name);
    }


    /**
     *  Gets all pipelines keyed by their name
     *
     *@return    The pipelines
     */
    public Map getPipelines() {
        return pipelines;
    }


    /**  SAX handler class that loads the pipeline definitions */
    private class PipelineParser extends DefaultHandler {

        private MutablePipeline pipeline;
        private ArrayList trans;
        private BeanFactory factory;


        /**
         *  Constructor
         *
         *@param  factory  Bean factory to get pipelines from
         */
        public PipelineParser(BeanFactory factory) {
            this.factory = factory;
        }


        public void startElement(String uri, String loc, String raw, Attributes a)
            throws SAXException {

            StageBuilder builder = null;
            try {
                if (raw.equals(pipelineTag)) {
                    pipeline = (MutablePipeline) factory.getBean(pipelineBean);
                    pipeline.setName(a.getValue("name"));
                } else if (raw.equals(generatorTag)) {
                    builder = (StageBuilder) factory.getBean(stageBuilderBean);
                    builder.setStageId(generatorTag + "." + a.getValue("type"));
                    pipeline.setGeneratorBuilder(builder);
                } else if (raw.equals(transformerTag)) {
                    builder = (StageBuilder) factory.getBean(stageBuilderBean);
                    builder.setStageId(transformerTag + "." + a.getValue("type"));
                    pipeline.addTransformerBuilder(builder);
                } else if (raw.equals(serializerTag)) {
                    builder = (StageBuilder) factory.getBean(stageBuilderBean);
                    builder.setStageId(serializerTag + "." + a.getValue("type"));
                    pipeline.setSerializerBuilder(builder);
                }
            } catch (StageNotFoundException ex) {
                throw new SAXException(ex.getMessage());
            }
            if (builder != null) {
                String name;
                for (int x = 0; x < a.getLength(); x++) {
                    name = a.getQName(x);
                    if (!"type".equals(name)) {
                        builder.addParameter(name, a.getValue(x));
                    }
                }
            }
        }


        public void endElement(String uri, String loc, String raw)
            throws SAXException {
            if (raw.equals(pipelineTag)) {
                pipelines.put(pipeline.getName(), pipeline);
            }
        }


        public void endDocument()
            throws SAXException {
            pipelines = Collections.unmodifiableMap(pipelines);
        }
    }

}

